package io.youngledo.usb.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/// 凭据管理器 - 安全地保存和管理存储服务的访问密钥
/// 使用`AES-256-CBC`加密存储敏感信息
///
public class CredentialManager {

    private static final Logger logger = LoggerFactory.getLogger(CredentialManager.class);
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    private static final String CREDENTIALS_DIR = ".universal-storage-browser";
    private static final String CREDENTIALS_FILE = "credentials.enc";
    private static final String KEY_FILE = "master.key";

    private static CredentialManager instance;
    private SecretKey masterKey;
    private Path credentialsFile;
    private Path keyFile;

    private CredentialManager() {
        initializeFiles();
        loadOrCreateMasterKey();
    }

    public static synchronized CredentialManager getInstance() {
        if (instance == null) {
            instance = new CredentialManager();
        }
        return instance;
    }

    private void initializeFiles() {
        String homeDir = System.getProperty("user.home");
        Path credentialsDir = Paths.get(homeDir, CREDENTIALS_DIR);
        credentialsFile = credentialsDir.resolve(CREDENTIALS_FILE);
        keyFile = credentialsDir.resolve(KEY_FILE);

        try {
            Files.createDirectories(credentialsDir);
        } catch (IOException e) {
            logger.error("Failed to create credentials directory", e);
            throw new RuntimeException("Cannot initialize credential storage", e);
        }
    }

    private void loadOrCreateMasterKey() {
        if (Files.exists(keyFile)) {
            try {
                byte[] keyBytes = Files.readAllBytes(keyFile);
                String encodedKey = new String(keyBytes);
                byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
                masterKey = new SecretKeySpec(decodedKey, ALGORITHM);
                logger.info("Master key loaded successfully");
            } catch (IOException e) {
                logger.error("Failed to load master key", e);
                createNewMasterKey();
            }
        } else {
            createNewMasterKey();
        }
    }

    private void createNewMasterKey() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
            keyGenerator.init(256);
            masterKey = keyGenerator.generateKey();

            String encodedKey = Base64.getEncoder().encodeToString(masterKey.getEncoded());
            Files.write(keyFile, encodedKey.getBytes());

            // 设置文件权限（仅限Unix系统）
            try {
                Runtime.getRuntime().exec(new String[]{"chmod", "600", keyFile.toString()});
            } catch (IOException e) {
                logger.warn("Failed to set file permissions", e);
            }

            logger.info("New master key created and saved");
        } catch (Exception e) {
            logger.error("Failed to create master key", e);
            throw new RuntimeException("Cannot create master key", e);
        }
    }

    /// 保存存储服务凭据
    ///
    public void saveCredentials(String connectionName, String endpoint, String accessKey, String secretKey, String region) {
        Map<String, String> allCredentials = loadAllCredentials();

        // 加密敏感信息
        String encryptedAccessKey = encrypt(accessKey);
        String encryptedSecretKey = encrypt(secretKey);

        // 保存凭据信息（endpoint和region不加密，accessKey和secretKey加密）
        allCredentials.put(connectionName + ".endpoint", endpoint);
        allCredentials.put(connectionName + ".accessKey", encryptedAccessKey);
        allCredentials.put(connectionName + ".secretKey", encryptedSecretKey);
        allCredentials.put(connectionName + ".region", region != null ? region : "");

        saveAllCredentials(allCredentials);
        logger.info("Credentials saved for connection: {}", connectionName);
    }

    /// 加载存储服务凭据
    ///
    public StoredCredential loadCredentials(String connectionName) {
        Map<String, String> allCredentials = loadAllCredentials();

        String endpoint = allCredentials.get(connectionName + ".endpoint");
        String encryptedAccessKey = allCredentials.get(connectionName + ".accessKey");
        String encryptedSecretKey = allCredentials.get(connectionName + ".secretKey");
        String region = allCredentials.get(connectionName + ".region");

        if (endpoint == null || encryptedAccessKey == null || encryptedSecretKey == null) {
            return null;
        }

        try {
            String accessKey = decrypt(encryptedAccessKey);
            String secretKey = decrypt(encryptedSecretKey);

            return new StoredCredential(endpoint, accessKey, secretKey, region);
        } catch (Exception e) {
            logger.error("Failed to decrypt credentials for: {}", connectionName, e);
            return null;
        }
    }

    /// 获取所有已保存的连接名称
    ///
    public java.util.Set<String> getSavedConnectionNames() {
        Map<String, String> allCredentials = loadAllCredentials();
        java.util.Set<String> connectionNames = new java.util.HashSet<>();

        for (String key : allCredentials.keySet()) {
            if (key.endsWith(".endpoint")) {
                String connectionName = key.substring(0, key.length() - ".endpoint".length());
                connectionNames.add(connectionName);
            }
        }

        return connectionNames;
    }

    /// 删除保存的凭据
    ///
    public void deleteCredentials(String connectionName) {
        Map<String, String> allCredentials = loadAllCredentials();

        allCredentials.remove(connectionName + ".endpoint");
        allCredentials.remove(connectionName + ".accessKey");
        allCredentials.remove(connectionName + ".secretKey");
        allCredentials.remove(connectionName + ".region");

        saveAllCredentials(allCredentials);
        logger.info("Credentials deleted for connection: {}", connectionName);
    }

    private Map<String, String> loadAllCredentials() {
        Map<String, String> credentials = new HashMap<>();

        if (!Files.exists(credentialsFile)) {
            return credentials;
        }

        try {
            Properties props = new Properties();
            try (InputStream is = Files.newInputStream(credentialsFile)) {
                props.load(is);
            }

            for (String key : props.stringPropertyNames()) {
                credentials.put(key, props.getProperty(key));
            }
        } catch (IOException e) {
            logger.error("Failed to load credentials file", e);
        }

        return credentials;
    }

    private void saveAllCredentials(Map<String, String> credentials) {
        try {
            Properties props = new Properties();
            for (Map.Entry<String, String> entry : credentials.entrySet()) {
                props.setProperty(entry.getKey(), entry.getValue());
            }

            try (OutputStream os = Files.newOutputStream(credentialsFile)) {
                props.store(os, "Universal Storage Browser - Encrypted Credentials");
            }

            // 设置文件权限（仅限Unix系统）
            try {
                Runtime.getRuntime().exec(new String[]{"chmod", "600", credentialsFile.toString()});
            } catch (IOException e) {
                logger.warn("Failed to set file permissions", e);
            }

        } catch (IOException e) {
            logger.error("Failed to save credentials file", e);
            throw new RuntimeException("Cannot save credentials", e);
        }
    }

    private String encrypt(String plaintext) {
        try {
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);

            // 生成随机IV
            byte[] iv = new byte[16];
            new SecureRandom().nextBytes(iv);
            IvParameterSpec ivSpec = new IvParameterSpec(iv);

            cipher.init(Cipher.ENCRYPT_MODE, masterKey, ivSpec);
            byte[] encrypted = cipher.doFinal(plaintext.getBytes());

            // 将IV和加密数据组合，然后Base64编码
            byte[] combined = new byte[iv.length + encrypted.length];
            System.arraycopy(iv, 0, combined, 0, iv.length);
            System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);

            return Base64.getEncoder().encodeToString(combined);
        } catch (Exception e) {
            logger.error("Failed to encrypt data", e);
            throw new RuntimeException("Encryption failed", e);
        }
    }

    private String decrypt(String encryptedText) {
        try {
            byte[] combined = Base64.getDecoder().decode(encryptedText);

            // 分离IV和加密数据
            byte[] iv = new byte[16];
            byte[] encrypted = new byte[combined.length - 16];
            System.arraycopy(combined, 0, iv, 0, 16);
            System.arraycopy(combined, 16, encrypted, 0, encrypted.length);

            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            cipher.init(Cipher.DECRYPT_MODE, masterKey, ivSpec);

            byte[] decrypted = cipher.doFinal(encrypted);
            return new String(decrypted);
        } catch (Exception e) {
            logger.error("Failed to decrypt data", e);
            throw new RuntimeException("Decryption failed", e);
        }
    }

    /// 存储的凭据数据类
    ///
    public static class StoredCredential {
        private final String endpoint;
        private final String accessKey;
        private final String secretKey;
        private final String region;

        public StoredCredential(String endpoint, String accessKey, String secretKey, String region) {
            this.endpoint = endpoint;
            this.accessKey = accessKey;
            this.secretKey = secretKey;
            this.region = region;
        }

        public String getEndpoint() { return endpoint; }
        public String getAccessKey() { return accessKey; }
        public String getSecretKey() { return secretKey; }
        public String getRegion() { return region; }
    }
}